package bsh.compilation;

import java.math.BigDecimal;

import org.objectweb.asm.MethodVisitor;

import bsh.CompilationState;
import bsh.CompiledScript;
import bsh.CompiledScriptHelper;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.JavaMethodDescriptor;
import bsh.meta.MethodDescriptor;
import bsh.meta.TypeTransformation;
import bsh.meta.TypeWrapper;
import bsh.utils.reflection.transform.TypeTransformationFactory;

/**
 * transformacoes para BigDecimal
 * 
 * @author takeshi
 */
public enum BigDecimalTransformations implements TypeTransformationFactory {

    LONG_TO_BIG_DECIMAL {

        /**
         * {@inheritDoc}
         */
        public TypeTransformation getTypeTransformation(TypeWrapper arg0, TypeWrapper arg1) {
            return new AbstractBigDecimalTransformation() {

                public TypeWrapper transformStackTop(MethodVisitor visitor, TypeWrapper fromType, CompilationState state)
                        throws CompilationFailedException {
                    state.assertStackTopType(JavaClassWrapper.LONG_WRAPPER);
                    BIG_DECIMAL_VALUE_OF_LONG.writeInvoke(visitor, state);
                    state.assertStackTopType(JavaClassWrapper.BIG_DECIMAL_WRAPPER);
                    return BIG_DECIMAL_WRAPPER;
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        public boolean isApplicable(TypeWrapper arg0, TypeWrapper arg1) {
            return JavaClassWrapper.LONG_WRAPPER.equals(arg0) && JavaClassWrapper.wrapperFor(BigDecimal.class).equals(arg1);
        }

    },
    DOUBLE_TO_BIG_DECIMAL {

        /**
         * {@inheritDoc}
         */
        public TypeTransformation getTypeTransformation(TypeWrapper arg0, TypeWrapper arg1) {
            return new AbstractBigDecimalTransformation() {

                public TypeWrapper transformStackTop(MethodVisitor visitor, TypeWrapper fromType, CompilationState state)
                        throws CompilationFailedException {
                    state.assertStackTopType(JavaClassWrapper.DOUBLE_WRAPPER);
                    state.getUnit().thisType().loadThis(visitor, state);
                    TO_BIG_DECIMAL_DESCRIPTOR.writeInvoke(visitor, state);
                    state.assertStackTopType(JavaClassWrapper.BIG_DECIMAL_WRAPPER);
                    return BIG_DECIMAL_WRAPPER;
                }

            };
        }

        /**
         * {@inheritDoc}
         */
        public boolean isApplicable(TypeWrapper arg0, TypeWrapper arg1) {
            return JavaClassWrapper.DOUBLE_WRAPPER.equals(arg0) && JavaClassWrapper.wrapperFor(BigDecimal.class).equals(arg1);
        }

    };
    private static final TypeWrapper BIG_DECIMAL_WRAPPER = JavaClassWrapper.wrapperFor(BigDecimal.class);

    /**
     * {@link TypeTransformation}
     * 
     * @author takeshi
     */
    private abstract static class AbstractBigDecimalTransformation implements TypeTransformation {

        /**
         * {@inheritDoc}
         */
        public boolean acceptsType(TypeWrapper type) {
            return type.equals(BIG_DECIMAL_WRAPPER);
        }

        /**
         * {@inheritDoc}
         */
        public TypeWrapper toType(TypeWrapper fromType) {
            return BIG_DECIMAL_WRAPPER;
        }
    }

    private static final MethodDescriptor TO_BIG_DECIMAL_DESCRIPTOR;

    private static final MethodDescriptor BIG_DECIMAL_VALUE_OF_LONG;

    static {
        try {
            BIG_DECIMAL_VALUE_OF_LONG = JavaMethodDescriptor.findMethod(BigDecimal.class, "valueOf", long.class);
            TO_BIG_DECIMAL_DESCRIPTOR =
                    JavaMethodDescriptor.findMethod(CompiledScriptHelper.class, "toBigDecimal", double.class, CompiledScript.class);
        } catch (NoSuchMethodException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

}
